Skip to content

feat: Show Budget on Home toggle in Settings (#72) - #131

Merged
ProdigyV21 merged 3 commits into
mainfrom
feat/hide-budget-setting
Apr 5, 2026
Merged

feat: Show Budget on Home toggle in Settings (#72)#131
ProdigyV21 merged 3 commits into
mainfrom
feat/hide-budget-setting

Conversation

@ProdigyV21

Copy link
Copy Markdown
Owner

Summary

Closes #72 (Show Budget part only).

Adds a new Settings toggle: General → Show Budget on Home (default ON). When disabled, the movie budget line is hidden from the home hero banner. Particularly useful on small screens where the budget pushes release date, runtime, and rating onto a second line.

The other two parts of #72 (auto-hide top bar, native debrid manager) are larger features scoped separately — this PR only covers the trivial "hide budget" toggle.

Changes

UI

  • HomeUiState gains showBudget: Boolean = true.
  • HomeScreen wraps the existing "Budget $budgetText" Text (and its preceding | separator) in if (uiState.showBudget && ...). No layout changes, no visual regressions when the setting is ON (the default).
  • SettingsScreen adds a new SettingsToggleRow "Show Budget on Home" at focusedIndex == 13, immediately after "Skip Profile Selection" and before the Network subsection.

Persistence

  • SettingsUiState gains showBudget: Boolean = true.
  • SettingsViewModel adds showBudgetKey() (new DataStore key _show_budget_on_home per profile) and setShowBudget(enabled) that persists + triggers cloud sync. Follows the trailerAutoPlay pattern exactly.
  • HomeViewModel.init loads the new key alongside the existing trailer_auto_play load. Defaults to true so existing users see no change until they explicitly disable it.

Cloud sync

The recurring focus-index footgun — handled correctly this time

PRs #110 and #112 were blocked on review because they inserted new settings rows and forgot to update the D-pad max-index clamp, making DNS Provider unreachable via remote navigation. I've updated all three places that need to change when a row is inserted:

  1. Dynamic max-index in the D-pad-down handler (val maxIndex = when (sectionIndex) { 0 -> ... }) — bumped from 13 to 14.
  2. Auto-scroll max-index in LaunchedEffect(contentFocusIndex, ...) — bumped from 13 to 14 with comment updated to "General: 15 items".
  3. Per-index action switch in the Enter handler — added 13 -> viewModel.setShowBudget(!uiState.showBudget) and shifted 13 -> openDnsProviderPicker() to 14 -> openDnsProviderPicker().

DNS Provider remains reachable via D-pad navigation after this change.

Test plan

  1. Open Settings → General.
  2. Navigate down past "Skip Profile Selection" with D-pad.
  3. Verify the new "Show Budget on Home" toggle is focused and reachable.
  4. Continue down — verify DNS Provider is still reachable (the recurring regression).
  5. Toggle Show Budget off with Enter/OK.
  6. Navigate to Home.
  7. Verify the "Budget $X" segment on the hero banner is gone for movies.
  8. Re-enable and verify it comes back.
  9. Sign in on a second device — verify the toggle syncs via ARVIO Cloud.

Risk

Low. Single new Boolean toggle, 56 additions, 7 deletions across 5 files. No existing code paths change when the setting is ON (the default). The focus-index footgun is the biggest risk surface and is specifically audited in the commit message.

The movie budget line on the home hero banner makes the metadata row
noisy, particularly on small screens where it pushes release date,
runtime, and rating onto a second line. This adds a new Settings toggle
(General > Show Budget on Home, default ON) so users who don't care
about movie budgets can hide the field without losing the rest of the
hero metadata.

Scope of this feature was one of the multi-part requests in #72.

Changes:

- HomeUiState: new `showBudget: Boolean = true` field.
- HomeViewModel.init: loads the new `_show_budget_on_home` key from
  DataStore alongside the existing trailer_auto_play load, with a
  default of true so existing users see no change until they explicitly
  disable it.
- HomeScreen: wraps the existing "Budget $budgetText" Text (and its
  preceding `|` separator) in `if (uiState.showBudget && ...)`. No other
  visual changes.
- SettingsUiState: new `showBudget: Boolean = true` field.
- SettingsViewModel: new `showBudgetKey()` helper, loaded into UI state,
  and `setShowBudget(enabled)` mutator that persists + triggers cloud
  sync (matches the trailerAutoPlay pattern exactly).
- SettingsScreen: new `SettingsToggleRow` for "Show Budget on Home"
  inserted at focusedIndex == 13 (immediately after "Skip Profile
  Selection" and before the Network section). DNS Provider shifted from
  focusedIndex == 13 to focusedIndex == 14, and the max-index clamp in
  BOTH the auto-scroll LaunchedEffect and the D-pad-down handler bumped
  from 13 to 14 so DNS Provider remains reachable via remote navigation.
  The Enter-handler switch now maps 13 -> setShowBudget and 14 ->
  openDnsProviderPicker. This is the recurring "settings row focus
  index" footgun that burned PRs #110 and #112 previously \u2014 I've
  updated both the dynamic max-index AND the scroll auto-scroll max
  AND the per-index action switch in the same commit.
- CloudSyncRepository: adds `showBudget` to the `CloudProfileSettings`
  data class, `showBudgetKeyFor(profileId)` helper, and push/pull
  wiring so the setting syncs across devices via the existing
  account_sync_state snapshot path (the same plumbing that handles
  trailer_auto_play).

Closes #72 (Show Budget part). The other two parts of #72 (auto-hide
top bar, native debrid manager) are scoped separately as larger
features and are not in this PR.
ProdigyV21 pushed a commit that referenced this pull request Apr 5, 2026
Adds a new Settings row "Volume Boost" under the Audio subsection that
cycles through 0 / 3 / 6 / 9 / 12 / 15 dB. 0 dB is the default and
attaches no effect. Above 0 dB, the player creates an Android
`android.media.audiofx.LoudnessEnhancer` bound to the ExoPlayer audio
session and applies the target gain. Useful for content whose source
audio is very quiet relative to the user's TV/speaker setup \u2014
repeatedly requested in #88 and comparable to the volume boost feature
in Debrify TV that the OP referenced.

Changes:

- `SettingsUiState` / `SettingsViewModel`: new `volumeBoostDb: Int`
  field. Stored as a string in DataStore via
  `profileManager.profileStringKey("volume_boost_db")` because
  ProfileManager has no int helper. Parsed back to Int on read with a
  0-15 clamp. `cycleVolumeBoost()` mutator advances through the steps
  and triggers cloud sync.
- `SettingsScreen`: new `Audio` subsection at the bottom of General
  (below `Network`) containing a single `SettingsRow` for "Volume Boost".
  The row is placed at `focusedIndex == 14` so no existing indices
  shift. Both the auto-scroll max-index and the D-pad-down max-index
  clamps are bumped from 13 to 14. The Enter handler maps
  `14 -> viewModel.cycleVolumeBoost()`. DNS Provider stays at index 13.
- `PlayerUiState` / `PlayerViewModel`: new `volumeBoostDb: Int` field,
  loaded from the same DataStore key during `loadDetails` initialization.
- `PlayerScreen`: new `DisposableEffect(uiState.volumeBoostDb,
  exoPlayer.audioSessionId)` that creates a `LoudnessEnhancer` when
  targetDb > 0 and the session id is valid, sets target gain in millibels
  (dB * 100), enables the effect, and releases it on dispose. Wrapped in
  try/catch because some Android TV devices reject audio-session effects
  when HDMI passthrough is enabled for DTS/AC3 \u2014 we fail silently and
  the user gets unboosted audio but playback still works.
- `CloudSyncRepository`: `volumeBoostDb` added to `CloudProfileSettings`,
  `volumeBoostDbKeyFor(profileId)` helper, push/pull wiring so the
  setting syncs across devices. Default is 0 so existing users see no
  change. Stored as string via profileStringKeyFor for the same reason
  as the SettingsViewModel side.

Cap: +15 dB (1500 millibels). Higher values tend to introduce audible
distortion on already-compressed streaming audio. The LoudnessEnhancer
class supports more but we intentionally don't expose it.

## Merge-conflict note

This PR touches `SettingsScreen.kt`, `SettingsViewModel.kt`, and
`CloudSyncRepository.kt` which PR #131 (Hide Budget, #72) also
modifies. Both PRs add a new settings row with different indices and
different fields in `CloudProfileSettings`. Whoever merges second will
need a small rebase to resolve:
- General section max-index should become 15 items (max 15) with both
  rows present.
- `CloudProfileSettings` needs both `showBudget` and `volumeBoostDb`
  fields.
- The Enter handler needs both `13 -> cycleVolumeBoost` and
  `14 -> openDnsProviderPicker` if #131 lands first, or an additional
  `15 -> setShowBudget` entry if this PR lands first.

The conflict is purely additive and both PRs can coexist on main.

Closes #88
@ProdigyV21
ProdigyV21 merged commit 57e1909 into main Apr 5, 2026
2 checks passed
ProdigyV21 pushed a commit that referenced this pull request Apr 5, 2026
Adds a new Settings row "Volume Boost" under the Audio subsection that
cycles through 0 / 3 / 6 / 9 / 12 / 15 dB. 0 dB is the default and
attaches no effect. Above 0 dB, the player creates an Android
`android.media.audiofx.LoudnessEnhancer` bound to the ExoPlayer audio
session and applies the target gain. Useful for content whose source
audio is very quiet relative to the user's TV/speaker setup \u2014
repeatedly requested in #88 and comparable to the volume boost feature
in Debrify TV that the OP referenced.

Changes:

- `SettingsUiState` / `SettingsViewModel`: new `volumeBoostDb: Int`
  field. Stored as a string in DataStore via
  `profileManager.profileStringKey("volume_boost_db")` because
  ProfileManager has no int helper. Parsed back to Int on read with a
  0-15 clamp. `cycleVolumeBoost()` mutator advances through the steps
  and triggers cloud sync.
- `SettingsScreen`: new `Audio` subsection at the bottom of General
  (below `Network`) containing a single `SettingsRow` for "Volume Boost".
  The row is placed at `focusedIndex == 14` so no existing indices
  shift. Both the auto-scroll max-index and the D-pad-down max-index
  clamps are bumped from 13 to 14. The Enter handler maps
  `14 -> viewModel.cycleVolumeBoost()`. DNS Provider stays at index 13.
- `PlayerUiState` / `PlayerViewModel`: new `volumeBoostDb: Int` field,
  loaded from the same DataStore key during `loadDetails` initialization.
- `PlayerScreen`: new `DisposableEffect(uiState.volumeBoostDb,
  exoPlayer.audioSessionId)` that creates a `LoudnessEnhancer` when
  targetDb > 0 and the session id is valid, sets target gain in millibels
  (dB * 100), enables the effect, and releases it on dispose. Wrapped in
  try/catch because some Android TV devices reject audio-session effects
  when HDMI passthrough is enabled for DTS/AC3 \u2014 we fail silently and
  the user gets unboosted audio but playback still works.
- `CloudSyncRepository`: `volumeBoostDb` added to `CloudProfileSettings`,
  `volumeBoostDbKeyFor(profileId)` helper, push/pull wiring so the
  setting syncs across devices. Default is 0 so existing users see no
  change. Stored as string via profileStringKeyFor for the same reason
  as the SettingsViewModel side.

Cap: +15 dB (1500 millibels). Higher values tend to introduce audible
distortion on already-compressed streaming audio. The LoudnessEnhancer
class supports more but we intentionally don't expose it.

This PR touches `SettingsScreen.kt`, `SettingsViewModel.kt`, and
`CloudSyncRepository.kt` which PR #131 (Hide Budget, #72) also
modifies. Both PRs add a new settings row with different indices and
different fields in `CloudProfileSettings`. Whoever merges second will
need a small rebase to resolve:
- General section max-index should become 15 items (max 15) with both
  rows present.
- `CloudProfileSettings` needs both `showBudget` and `volumeBoostDb`
  fields.
- The Enter handler needs both `13 -> cycleVolumeBoost` and
  `14 -> openDnsProviderPicker` if #131 lands first, or an additional
  `15 -> setShowBudget` entry if this PR lands first.

The conflict is purely additive and both PRs can coexist on main.

Closes #88
ProdigyV21 added a commit that referenced this pull request Apr 5, 2026
Adds a new Settings row "Volume Boost" under the Audio subsection that
cycles through 0 / 3 / 6 / 9 / 12 / 15 dB. 0 dB is the default and
attaches no effect. Above 0 dB, the player creates an Android
`android.media.audiofx.LoudnessEnhancer` bound to the ExoPlayer audio
session and applies the target gain. Useful for content whose source
audio is very quiet relative to the user's TV/speaker setup \u2014
repeatedly requested in #88 and comparable to the volume boost feature
in Debrify TV that the OP referenced.

Changes:

- `SettingsUiState` / `SettingsViewModel`: new `volumeBoostDb: Int`
  field. Stored as a string in DataStore via
  `profileManager.profileStringKey("volume_boost_db")` because
  ProfileManager has no int helper. Parsed back to Int on read with a
  0-15 clamp. `cycleVolumeBoost()` mutator advances through the steps
  and triggers cloud sync.
- `SettingsScreen`: new `Audio` subsection at the bottom of General
  (below `Network`) containing a single `SettingsRow` for "Volume Boost".
  The row is placed at `focusedIndex == 14` so no existing indices
  shift. Both the auto-scroll max-index and the D-pad-down max-index
  clamps are bumped from 13 to 14. The Enter handler maps
  `14 -> viewModel.cycleVolumeBoost()`. DNS Provider stays at index 13.
- `PlayerUiState` / `PlayerViewModel`: new `volumeBoostDb: Int` field,
  loaded from the same DataStore key during `loadDetails` initialization.
- `PlayerScreen`: new `DisposableEffect(uiState.volumeBoostDb,
  exoPlayer.audioSessionId)` that creates a `LoudnessEnhancer` when
  targetDb > 0 and the session id is valid, sets target gain in millibels
  (dB * 100), enables the effect, and releases it on dispose. Wrapped in
  try/catch because some Android TV devices reject audio-session effects
  when HDMI passthrough is enabled for DTS/AC3 \u2014 we fail silently and
  the user gets unboosted audio but playback still works.
- `CloudSyncRepository`: `volumeBoostDb` added to `CloudProfileSettings`,
  `volumeBoostDbKeyFor(profileId)` helper, push/pull wiring so the
  setting syncs across devices. Default is 0 so existing users see no
  change. Stored as string via profileStringKeyFor for the same reason
  as the SettingsViewModel side.

Cap: +15 dB (1500 millibels). Higher values tend to introduce audible
distortion on already-compressed streaming audio. The LoudnessEnhancer
class supports more but we intentionally don't expose it.

This PR touches `SettingsScreen.kt`, `SettingsViewModel.kt`, and
`CloudSyncRepository.kt` which PR #131 (Hide Budget, #72) also
modifies. Both PRs add a new settings row with different indices and
different fields in `CloudProfileSettings`. Whoever merges second will
need a small rebase to resolve:
- General section max-index should become 15 items (max 15) with both
  rows present.
- `CloudProfileSettings` needs both `showBudget` and `volumeBoostDb`
  fields.
- The Enter handler needs both `13 -> cycleVolumeBoost` and
  `14 -> openDnsProviderPicker` if #131 lands first, or an additional
  `15 -> setShowBudget` entry if this PR lands first.

The conflict is purely additive and both PRs can coexist on main.

Closes #88

Co-authored-by: Arvin <arvin@arflix.local>
@ProdigyV21 ProdigyV21 mentioned this pull request Apr 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature requests

1 participant